using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;

using Atomic.Libraries.Mathematics;
using Atomic.Libraries.Plotting.SpacePlotting;
using Atomic.Structures.Symmetries;

namespace Atomic.Structures
{
	[Serializable]
	[DebuggerDisplay("{DebuggerDisplay}")]
	public class FaceCenteredCubicLattice : Lattice
	{
		public FaceCenteredCubicLattice(double a)
			: this(Basis.StandardBasis, a, SpaceMatrix.Identity)
		{
		}

		public FaceCenteredCubicLattice(Basis basis, double a, SpaceMatrix transform)
			: this(basis.Decompose(new Position(transform * new SpaceVector(0.0, 0.5, 0.5)) * a), basis.Decompose(new Position(transform * new SpaceVector(0.5, 0.0, 0.5))) * a, basis.Decompose(new Position(transform * new SpaceVector(0.5, 0.5, 0.0))) * a, a, transform)
		{
		}

		private FaceCenteredCubicLattice(Position a, Position b, Position c, double a0, SpaceMatrix transform)
			: base(a, b, c)
		{
			if (a0 <= 0.0)
			{
				throw new ArgumentException();
			}

			CheckOrthogonal(transform);

			A0 = a0;
			Transform = transform;

			Gamma = new Position(this, SymmetryPoints.Gamma);
			H = new Position(this, SymmetryPoints.H);
			N = new Position(this, SymmetryPoints.N);
			P = new Position(this, SymmetryPoints.P);
		}

		public static bool TryParse(Basis basis, SpaceVector a, SpaceVector b, SpaceVector c, out Lattice lattice)
		{
			lattice = null;

			// Chosen to be somewhat larger than VASP's output precision.
			double tol = 1.0e-7;

			// The reference lattice built directly from the vectors.
			Lattice lattice0 = new Lattice(basis, a, b, c);

			// Try creating a fcc lattice. The lattice constant of any fcc basis should have this relation to the norm.
			double a0 = SpaceMatrix.Norm(lattice0.PhysicalMap) / Math.Sqrt(1.5);
			if (a0 <= tol)
			{
				// Far too small lattice constant. Give up trying to parse.
				return false;
			}

			// Try a few common fcc representations. May need to add more.
			foreach (SpaceMatrix transform in new SpaceMatrix[] { SpaceMatrix.Identity, new SpaceMatrix(0.0, 0.0, 1.0, 0.0, 1.0, 0.0, -1.0, 0.0, 0.0), new SpaceMatrix(0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0) })
			{
				lattice = new FaceCenteredCubicLattice(basis, a0, transform);
				if (SpaceMatrix.Norm(lattice0.PhysicalMap - lattice.PhysicalMap) < tol)
				{
					// The mapping is identical. Parsed successfully.
					return true;
				}
			}

			return false;
		}

		protected override Lattice CreateReciprocalLattice()
		{
			//Lattice r0 = base.CreateReciprocalLattice();
			//Lattice r = new BodyCenteredCubicLattice(Basis.StandardBasis, 4.0 * Math.PI / A0, Transform);

			// http://math.stackexchange.com/a/61495
			return new BodyCenteredCubicLattice(Basis.StandardBasis, 4.0 * Math.PI / A0, Transform);
		}

		public IEnumerable<ISpacePlot> PlotWignerSeitzCell()
		{
			throw new NotImplementedException();
			/*// Point marking two of the boundary lines of the Wigner-Seitz cell. By symmetry this is sufficient to draw the entire cell.
			Position p1 = new Position(this, H.Coordinates);
			Position p2 = new Position(this, P.Coordinates);

			// Needs an atom a the origin to determine symmetries; so just add an arbitrary atom.
			foreach (SpaceGroupSymmetryOperation o in new Structure(this).Add(Atom.Vacancy, SpaceVector.Zero).SpaceGroup.SymmetryOperations)
			{
				Position q1 = o.Transform(p1);
				Position q2 = o.Transform(p2);
				yield return new SpaceLine(q1.PhysicalCoordinates, q2.PhysicalCoordinates);
			}

			// Plot symmetry lines (i.e. boundary of the IBZ).
			yield return new SpaceLine(Gamma.PhysicalCoordinates, H.PhysicalCoordinates);
			yield return new SpaceLine(Gamma.PhysicalCoordinates, N.PhysicalCoordinates);
			yield return new SpaceLine(Gamma.PhysicalCoordinates, P.PhysicalCoordinates);
			yield return new SpaceLine(H.PhysicalCoordinates, N.PhysicalCoordinates);
			yield return new SpaceLine(H.PhysicalCoordinates, P.PhysicalCoordinates);
			yield return new SpaceLine(N.PhysicalCoordinates, P.PhysicalCoordinates);*/
		}

		/// <summary>
		/// Lattice constant.
		/// </summary>
		public double A0
		{
			get;
			private set;
		}

		/// <summary>
		/// The orthogonal transform matrix from the standard lattice vector representation.
		/// </summary>
		public SpaceMatrix Transform
		{
			get;
			private set;
		}

		public Position Gamma
		{
			get;
			private set;
		}

		public Position H
		{
			get;
			private set;
		}

		public Position N
		{
			get;
			private set;
		}

		public Position P
		{
			get;
			private set;
		}

		private string DebuggerDisplay
		{
			get
			{
				return string.Format(CultureInfo.InvariantCulture, "fcc, A = ({0:f4}, {1:f4}, {2:f4}), B = ({3:f4}, {4:f4}, {5:f4}), C = ({6:f4}, {7:f4}, {8:f4})",
					A.PhysicalCoordinates.X, A.PhysicalCoordinates.Y, A.PhysicalCoordinates.Z, 
					B.PhysicalCoordinates.X, B.PhysicalCoordinates.Y, B.PhysicalCoordinates.Z, 
					C.PhysicalCoordinates.X, C.PhysicalCoordinates.Y, C.PhysicalCoordinates.Z);
			}
		}

		public static class SymmetryPoints
		{
			static SymmetryPoints()
			{
				Gamma = new SpaceVector(0.0, 0.0, 0.0);
				H = new SpaceVector(0.5, -0.5, 0.5);
				N = new SpaceVector(0.0, 0.0, 0.5);
				P = new SpaceVector(0.25, 0.25, 0.25);
			}

			public static SpaceVector Gamma
			{
				get;
				private set;
			}

			public static SpaceVector H
			{
				get;
				private set;
			}

			public static SpaceVector N
			{
				get;
				private set;
			}

			public static SpaceVector P
			{
				get;
				private set;
			}
		}
	}
}
